package uk.ac.bath.domains.mover;



import java.util.Collections;
import java.util.Vector;


import uk.ac.bath.base.BasicPopulationEnvironment;
import uk.ac.bath.base.Cell;
import uk.ac.bath.base.CellFactoryIF;
import uk.ac.bath.base.CellFitnessComparator;
import uk.ac.bath.base.Global;
import uk.ac.bath.base.Point;
import uk.ac.bath.base.Space;
import uk.ac.bath.domains.misc.FoodSupply;
import uk.ac.bath.domains.misc.Point2DInt;

import uk.ac.bath.util.MyRandom;
import uk.ac.bath.util.TweakableDouble;
import uk.ac.bath.util.TweakableInt;

abstract public class Environment extends BasicPopulationEnvironment {
    // Observable implements EnvironmentIF {

    public TweakableInt maxPopulation =
            new TweakableInt(tweaks, 1, 10000, 5000, "PopulationLimit");
    public TweakableDouble seedProb =
            new TweakableDouble(tweaks, 0.0, 1.0, 0.1, 0.001, "SeedProb");
  
    protected Cell cellArray[][][];
    int popCount;
    protected CellFactoryIF factory;
    private Space space;

    public Environment(Space space, CellFactoryIF factory) {
        super();
        this.factory=factory;
        this.space=space;
        cellArray = new Cell[space.sizeX][space.sizeY][space.sizeZ];
        tweaks.addAll(factory.getTweaks());
    }

     public Space getSpace() {
         return space;
     }

    final public synchronized int prune() {
        Collections.sort(population, new CellFitnessComparator());

        Collections.reverse(population); //.reverseOrder(); // Collections.reverseOrder());

        int n = population.size();
        int newSize = n;

        for (int i = 0; i < n; i++) {
            Cell cell = (Cell) (population.elementAt(i));
            if (cell.getFitness() <= 0) {
                newSize = i;
                if (Global.debug != null) {
                    Global.debug.println(" Discarded " + (n - i) + " cells");
                }
                break;
            }
        }

        for (int i = newSize; i < n; i++) {
            Cell cell = (Cell) (population.elementAt(i));
            removeCell(cell);  // in case a subclass has a data structure 
        }

        population.setSize(newSize);

        return n;
    }

    final public Point randomEmptyAdjacent(Point p1) {
        Point2DInt p2 = (Point2DInt) p1;
        Vector<Point> v = new Vector<Point>();

        Point p = (Point) space.above(p2);
        if (p != null && cellAt(p) == null) {
            v.add(p);
        }
        p = space.below(p2);
        if (p != null && cellAt(p) == null) {
            v.add(p);
        }
        p = space.left(p2);
        if (p != null && cellAt(p) == null) {
            v.add(p);
        }
        p = space.right(p2);
        if (p != null && cellAt(p) == null) {
            v.add(p);
        }
        int n = v.size();
        if (n == 0) {
            return null;
        }
        return (Point) v.elementAt(MyRandom.nextInt(n));
    }

    final public void removeCell(Cell cell) {
        Point p = cell.getPoint();
        //	assert 	cellArray[p.getX()][p.getY()][p.getZ()]==cell;
        cellArray[p.getIX()][p.getIY()][p.getIZ()] = null;
        popCount--;
    }

    final public Cell cellAt(Point p) {
        if (!space.contains(p)) {
            return null;
        }
        return cellArray[p.getIX()][p.getIY()][p.getIZ()];
    }

 
    final public synchronized void addCell(Cell newCell) {
        // System.out.println("add cell");
        population.add(newCell);
        Point p = newCell.getPoint();
        //	assert cellArray[p.getX()][p.getY()][p.getZ()]==null;
        cellArray[p.getIX()][p.getIY()][p.getIZ()] = newCell;
        popCount++;
    }

    final int popCount() {
        return popCount;
    }

    final public int free() {
        return maxPopulation.intValue() - popCount;
    }

//    final public Vector<Cell> getPopulation() {
//        return population;
//    }

   

    abstract public FoodSupply getFoodSupply();

    abstract public Cell createRandomCell();

    public abstract void replenish();

    public abstract void mutateCell(Cell cell);

    public final void tick() {
        nTick++;
        int popSize = prune();
        if (popSize == 0 || MyRandom.nextDouble() < seedProb.getNumber().doubleValue()) {
            Cell c = createRandomCell();
            if (c != null) {
                addCell(c);
            }
        }
        popSize = population.size();
        for (int i = 0; i < popSize; i++) {
            Cell cell = (Cell) (population.elementAt(i));
            cell.tick();
        }
        replenish();
    }
}

